package ex1;

public class GOLThread extends Thread {
	/**
	 * Mini-board
	 */
	private Cell[][] threadBoard;

	/**
	 * Initial board and sizes
	 */
	private boolean[][] initialBoard;
	private int numOfBigBoardColumns;
	private int numOfBigBoardRows;
	
	/**
	 * Mini-board size (The part the thread is in charge of)
	 */
	private int rowStart;
	private int rowEnd;
	private int columnStart;
	private int columnEnd;
	
	private int numOfRows;
	private int numOfColumns;
	private int totalNumOfCells;
	
	/**
	 * Final board params
	 */
	private boolean[][] finalBoard;
	private int totalNumOfGenerations;
	private int doneCount; // number of cells that have been updated in the final board
	
	private Counter counter;
	
	/**
	 * Action Queue - all actions are cells that must be checked
	 * This is an INTERNAL queue used only by this thread
	 */
	private Queue<Action> innerQueue;
	
	/**
	 * Update Queue - all upadtes are incoming updates from other threads
	 * This is an SHARED queue used by all neighboring threads
	 */
	private SynchronizedQueue<Update> outerQueue;
	
	/**
	 * Neigboring update queues
	 */
	private SynchronizedQueue<Update>[][] neighborQueues;	
	
	public GOLThread(boolean[][] _initialBoard, int _numOfBigBoardRows, int _numOfBigBoardColumns,
			int _rowStart, int _rowEnd, int _columnStart, int _columnEnd,
			SynchronizedQueue<Update>[][] _Queues, boolean[][] _finalBoard, int _totalNumOfGenerations, Counter _counter) {
		initialBoard = _initialBoard;

		numOfBigBoardColumns = _numOfBigBoardColumns;
		numOfBigBoardRows = _numOfBigBoardRows;

		rowStart = _rowStart;
		rowEnd = _rowEnd;
		columnStart = _columnStart;
		columnEnd = _columnEnd;
		
		outerQueue = _Queues[1][1];
		
		neighborQueues = _Queues;
		
		finalBoard = _finalBoard;
		
		totalNumOfGenerations = _totalNumOfGenerations;
		
		counter = _counter;
	}
	
	
	@Override
	public void run() {
		initialize();
		while (doneCount != totalNumOfCells) {
			if ((!outerQueue.isEmpty()) || (innerQueue.isEmpty())) {
				popOuterQueue();
			} else {
				popInnerQueue();
			}
		}
		// Tell main that thread is done
		counter.tick();
	}


	/************************/
	/*	Private methods     */
	/************************/


	/**
	 * Initializer called at the start of the thread's run So as to not do these
	 * costly calculations in the constructor which doesn't run in parallel
	 */
	private void initialize() {
		numOfRows = rowEnd - rowStart + 1;
		numOfColumns = columnEnd - columnStart + 1;
		
		totalNumOfCells = numOfRows*numOfColumns;
		
		innerQueue = new Queue<Action>();
		
		threadBoard = new Cell[numOfRows][numOfColumns];
		
		// create and update cells of thread board and update gen, state and liveNeighbors
		for (int i = 0; i < numOfRows; i++) {
			for (int j = 0; j < numOfColumns; j++) {
				boolean state = initialBoard[rowStart + i][columnStart + j];
				threadBoard[i][j] =	new Cell(i, j, 0,state,0, getNumberOfRealNeighbors(i,j));
			}
		}
		
		// For all cells Update all neighbors about the state & generation
		for (int i = 0; i < numOfRows; i++) {
			for (int j = 0; j < numOfColumns; j++) {
				updateAllNeighbors(threadBoard[i][j]);
			}
		}
	}

	/**
	 * Pop action from innerQueue and take care of it.
	 */
	private void popInnerQueue() {
		Action action = innerQueue.dequeue();
		if (action == null) {
			return;
		}
		Cell cell = action.getCell();
		if (cell.canUpgrade()) {
			upgradeAndUpdate(cell);
		}
	}

	/**
	 * upgrade cell and update all neighbors about the change.
	 * @param cell - to be upgraded
	 */
	private void upgradeAndUpdate(Cell cell) {
		cell.upgrade();
		if (cell.getGeneration() == totalNumOfGenerations) {
			// Final update has just occurred
			finalBoard[rowStart + cell.getRow()][columnStart + cell.getColumn()] = cell.getState();
			doneCount++;
		} else {
			updateAllNeighbors(cell);
		}
	}
	
	/**
	 * Pop action from outerQueue and take care of it.
	 */
	private void popOuterQueue() {
		Update update = outerQueue.dequeue();
		if (update == null) {
			return;
		}
		
		// convert bigboard address to threadboard (RELATIVE)
		int row = update.getRow() - rowStart;
		int column = update.getColumn() - columnStart;
		
		boolean cellState = update.getState();
		int cellGeneration = update.getGeneration();
		
		// update all local cells neighboring the updating cell
		for (int i = row - 1; i <= row + 1; i++) {
			for (int j = column - 1; j <= column + 1; j++) {
				if ((i >= 0) && (i < numOfRows) && (j >= 0) && (j < numOfColumns)) {
					updateLocalNeighbor(cellState, cellGeneration, i, j);
				}
			}
		}
		
	}

	
	/**
	 * Update all neighbors of cell about generation change
	 * @param cell - the updating cell
	 */
	private void updateAllNeighbors(Cell cell) {
		int row = cell.getRow();
		int column = cell.getColumn();
		
		boolean cellState = cell.getState();
		int cellGeneration = cell.getGeneration();
		
		// queues to be updated about the change
		boolean[][] toUpdate = new boolean[3][3];
		
		
		//initialize to false all queues to be updated
		for (int i=0; i<3; i++) {
			for (int j=0; j<3; j++) {
				toUpdate[i][j] = false;
			}
		}
		
		// update all local cells neighboring updating cell and send update to all threads with neighboring cells
		for (int i = row -1; i <= row + 1; i++) {
			for (int j = column -1; j <= column + 1; j++) {
				// don't update self
				if ((i == row) && (j == column)) {
					continue;
				}
				// if neighboring cell not on this threads board
				if ((i < 0) || (j < 0) || (i >= numOfRows) || (j >= numOfColumns)) {
					markToUpdate(toUpdate, i, j);					
				} else {
					// for inner cells
					updateLocalNeighbor(cellState, cellGeneration, i, j);
				}
			}
		}

		//update all queues awaiting update
		for (int i=0; i<3; i++) {
			for (int j=0; j<3; j++) {
				if ((toUpdate[i][j]) && (neighborQueues[i][j] != null)) {
					updateNeighboringQueue(cell, i, j);
				}
			}
		}
	}

	/**
	 * Mark to update - find appropriate queue and mark to update it
	 * @param toUpdate - array of flags on updates of queues
	 * @param row - of cell needing update
	 * @param column - of cell needing update
	 */
	private void markToUpdate(boolean[][] toUpdate, int row, int column) {
		// update appropriate thread
		int neighborQueueRow = 1;
		int neighborQueueColumn = 1;
		if (row < 0) {
			neighborQueueRow = 0;
		}  else if (row >= numOfRows) {
			neighborQueueRow = 2;
		}
		
		if (column < 0) {
			neighborQueueColumn = 0;
		} else if (column >= numOfColumns) {
			neighborQueueColumn = 2;
		}
		toUpdate[neighborQueueRow][neighborQueueColumn] = true;
	}

	/**
	 * update neighboring queue about cell change
	 * @param cell - changing cell
	 * @param queueRow - row of queue
	 * @param queueColumn - column of queue
	 */
	private void updateNeighboringQueue(Cell cell, int queueRow, int queueColumn) {
		neighborQueues[queueRow][queueColumn].enqueue(
			new Update(rowStart + cell.getRow(), columnStart + cell.getColumn(), cell.getGeneration(), cell.getState()));
	}
	
	/**
	 * Update local neighbor (in the same thread)
	 * @param updatingCellState - state of the updating cell
	 * @param updatingCellGeneration - generation of the updating cell
	 * @param row - of the cell being updated (RELATIVE)
	 * @param column - of the cell being updated (RELATIVE)
	 */
	private void updateLocalNeighbor(boolean updatingCellState, int updatingCellGeneration,int row, int column) {
		boolean canUpgrade = threadBoard[row][column].neighborUpdate(updatingCellState, updatingCellGeneration);
		if (canUpgrade) {
			innerQueue.enqueue(new Action(threadBoard[row][column]));
		}
	}
	
	/**
	 * get number of real neighbors on big board - the big board's frame is not considered as real neighbors 
	 * @param i - row in thread board (relative to thread)
	 * @param j - column in thread board (relative to thread)
	 * @return number of real neighbors
	 */
	private int getNumberOfRealNeighbors(int i, int j) {
		int row = rowStart + i;
		int column = columnStart + j;
		int sum = 8;

		// if cell is on the top or bottom of big board then he doesn't really have three neighbors on that side
		if ((row == 0) || (row == numOfBigBoardRows - 1)) {
			sum = sum - 3;
		}
		
		// if cell is on the left or right of big board then he doesn't really have three neighbors on that side
		if ((column == 0) || (column == numOfBigBoardColumns - 1)) {
			sum = sum - 3;
		}
		
		// if cell is in the corner then we removed the corner non-exiting cell twice so we must add 1 to even out
		if (((row == 0) || (row == numOfBigBoardRows - 1)) && ((column == 0) || (column == numOfBigBoardColumns - 1))) {
			sum++;
		}
		
		return sum;
	}
}
